/**
 * Copyright 2008-2009. Chongqing Communications Industry Services Co.,Ltd Information Technology Branch. All rights
 * reserved. <a>http://www.cqcis.com</a>
 */
package com.cqcis.uip.base.framework.core.config.impl;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.framework.SynchronousBundleListener;
import org.osgi.service.packageadmin.PackageAdmin;

import com.cqcis.uip.base.framework.PropertiesConfigService;
import com.cqcis.uip.base.framework.core.config.PropertiesConfig;
import com.cqcis.uip.base.framework.core.config.PropertiesLocationUtil;
import com.cqcis.uip.base.framework.core.config.PropertiesRepositoryService;
import com.cqcis.uip.base.framework.utils.LoopThreadPoolExecutor;

/**
 * @author huangmy
 * @date   2008-4-8
 */
public class PropertiesChgListenerImpl implements PropertiesConfigService, BundleActivator, SynchronousBundleListener {
	private static Log log = LogFactory.getLog(PropertiesChgListenerImpl.class);
	private PropertiesRepositoryService prs; // 配置管理
	private final Map<Bundle, List<PropsConfigLocationEntry>> propertiesConfigs = new ConcurrentHashMap<Bundle, List<PropsConfigLocationEntry>>(); /*配置文件修改时间*/	
	private PackageAdmin packageAdmin; // 包管理
	private BundleContext context; // bundleContext
	private LoopThreadPoolExecutor listenPropertiesExecutor; // 配置文件监控线程池
	private ServiceRegistration prsServiceReg; // 配置管理服务注册
	private ServiceRegistration propertiesConfigServiceReg; // 配置管理服务注册
	
	public List<PropertiesConfig> get(Bundle b) {
		List<PropsConfigLocationEntry> pces = this.propertiesConfigs.get(b);
		if (pces != null) {
			List<PropertiesConfig> cs = new ArrayList<PropertiesConfig>();
			for (PropsConfigLocationEntry e : pces) {
				cs.add(e.getPropsConfig());
			}			
			return cs;
		}
		return null;
	}
	
	public void start(BundleContext context) throws Exception {
		this.context = context;
		// 配置管理
		this.prs = new PropertiesRepositoryServiceImpl();
		this.prsServiceReg = context.registerService(PropertiesRepositoryService.class.getName(), this.prs, null);
		this.propertiesConfigServiceReg = context.registerService(PropertiesConfigService.class.getName(), this, null);
		
		ServiceReference paRf = this.context.getServiceReference(PackageAdmin.class.getName());
		this.packageAdmin = (PackageAdmin)this.context.getService(paRf);
		
		// 配置监控配置文件
		this.listenPropertiesExecutor = LoopThreadPoolExecutor.getExecutor(2, 2, 60, 
				null, null, "Properties Monitor", 30 * 1000);
		
		this.listenPropertiesExecutor.execute(new Runnable() {
			public void run() {
				try {
					monitorChgPropertiesConfigs(propertiesConfigs);						
				} catch (Exception e) {
					log.error("monitorChgPropertiesConfigs Exception.", e);
				}
			}
		});	
		
		// 监控配置
		Bundle[] bundles = context.getBundles();
		if (bundles != null) {
			for (Bundle bundle : bundles) {
				if (bundle.getBundleId() == 0 || (bundle.getSymbolicName() != null 
						&& bundle.getSymbolicName().indexOf("org.eclipse") >= 0)) {
					continue;
				}
				saveConfiguration(bundle);
			}
		}
		
		context.addBundleListener(this);
	}
	
	public void stop(BundleContext context) throws Exception {		
		if (this.listenPropertiesExecutor != null) {
			this.listenPropertiesExecutor.shutdown();
		}
		
		if (this.prsServiceReg != null) {
			this.prsServiceReg.unregister();
		}
		
		if (this.propertiesConfigServiceReg != null) {
			this.propertiesConfigServiceReg.unregister();
		}
	}
	
	public void bundleChanged(BundleEvent event) {	
		if (event.getBundle().getBundleId() == 0 
				|| event.getBundle().getSymbolicName().indexOf("org.eclipse") >= 0) {
			return;
		}
		switch (event.getType()) {
		case BundleEvent.STARTED: {			
			// 保存当前的配置
			try {
				saveConfiguration(event.getBundle());
			} catch (Exception e) {
				log.error("save configuration Exception. ", e);
			}
			break;
		}
		case BundleEvent.STOPPING: {
			try {
				deleteConfiguration(event.getBundle());
			} catch (Exception e) {
				log.error("delete configuration Exception. ", e);
			}
		}
		default:
			break;
		}		
	}
	
	/**
	 * 保存配置
	 * @param bundle
	 * @throws Exception 
	 */
	private void saveConfiguration(Bundle bundle) throws Exception {
		if (propertiesConfigs.get(bundle) != null) {
			return;
		}
		List<PropsEntry> entries = generatePropsLocationsFor(bundle);
		if (entries != null) {
			for (PropsEntry entry : entries) {
				URL url = entry.getFromUrl();
				String propertiesLocation = entry.getToPropsLoction();
				String bundleLocation = entry.getBundleLocation();
				
                createPropFileFromURLTo(url, propertiesLocation);// 生成配置文件
				
				// 记录当前配置
                File file = new File(propertiesLocation);
                PropertiesConfig npc = PropertiesConfig.getPropertiesConfig(file);
                PropsConfigLocationEntry pcle = new PropsConfigLocationEntry(file, npc);

                List<PropsConfigLocationEntry> pcles = propertiesConfigs.get(bundle);
                if (pcles == null) {
                	pcles = new ArrayList<PropsConfigLocationEntry>();
                	propertiesConfigs.put(bundle, pcles);
                }
                pcles.add(pcle);
                
                List<String> propertiesLocations = prs.getPropertiesLocations(bundleLocation);
                if (propertiesLocations == null) {
                	propertiesLocations = new ArrayList<String>();
                	prs.put(bundleLocation, propertiesLocations);
                }
                propertiesLocations.add(propertiesLocation);
			}
		}
	}
	
	/**
	 * 删除配置
	 * 
	 * @param bundle
	 * @throws Exception
	 */
	private void deleteConfiguration(Bundle bundle) throws Exception {
		List<PropsEntry> entries = generatePropsLocationsFor(bundle);
		if (entries != null) {
			for (PropsEntry entry : entries) {	
				String bundleLocation = entry.getBundleLocation();
				String propertiesLocation = entry.getToPropsLoction();								
				propertiesConfigs.remove(bundle);
				prs.removeByBundleLocation(bundleLocation);
				prs.removeByPropertiesLocation(propertiesLocation);
			}
		}
	}
	
	/**
	 * 生成配置路径
	 * 
	 * @param bundle
	 * @return
	 */
	private List<PropsEntry> generatePropsLocationsFor(Bundle bundle) {		
		List<PropsEntry> list = new ArrayList<PropsEntry>();
		List<URL> urls = PropertiesLocationUtil.getBundleProperties(bundle);
		if (urls != null) {
			for (URL url : urls) {
				String bundleLocation = bundle.getLocation();
				String propertiesLocation = url.getPath();				
				propertiesLocation = getPropertiesLocation(bundleLocation, propertiesLocation);
				PropsEntry entry = new PropsEntry(url, propertiesLocation, bundleLocation);
				list.add(entry);
			}
		}
		
		return list;
	}
	
	class PropsEntry {
		private URL fromUrl;
		private String toPropsLoction;
		private String bundleLocation;
		
		public PropsEntry(URL fromUrl, String toPropsLoction, String bundleLocation) {
			this.fromUrl = fromUrl;
			this.toPropsLoction = toPropsLoction;
			this.bundleLocation = bundleLocation;
		}
		
		public URL getFromUrl() {
			return fromUrl;
		}
		public void setFromUrl(URL fromUrl) {
			this.fromUrl = fromUrl;
		}
		public String getToPropsLoction() {
			return toPropsLoction;
		}
		public void setToPropsLoction(String toPropsLoction) {
			this.toPropsLoction = toPropsLoction;
		}
		public String getBundleLocation() {
			return bundleLocation;
		}
		public void setBundleLocation(String bundleLocation) {
			this.bundleLocation = bundleLocation;
		}
	}
	
	/**
	 * 保存配置文件
	 * 
	 * @param url
	 * @param propertiesLocation
	 */
	private void createPropFileFromURLTo(URL url, String propertiesLocation) {
		File propFile = new File(propertiesLocation);
		if (!propFile.exists()) {
			OutputStream os = null;
			InputStream is = null;
			try {
			    os = new FileOutputStream(propFile);
			    is = url.openStream();
			    copyFileTo(is, os);
			} catch (Throwable t) {
			    log.info("Catch Exception in write properties file.", t);
			} finally {
				if (os != null) {
					try {
						os.close();
					} catch (IOException e1) {							
					}
				}
				
				if (is != null) {
					try {
						is.close();
					} catch (IOException e1) {
					}
				}
			}
		}
	}
	
	/**
	 * 文件复制
	 * 
	 * @param is
	 * @param os
	 */
	private void copyFileTo(InputStream is, OutputStream os) {
		try {			
			byte[] buf = new byte[1024];
			
			int count = 0;
			while ((count = is.read(buf)) > 0) {
				os.write(buf, 0, count);
			}
			
		} catch (Exception e) {			
			throw new RuntimeException("Process Configuration Exception.", e);
		}
	}
	
	/**
	 * 日志路径
	 * @param bundleLocation
	 * @param propertiesLocation
	 * @return
	 */
	private String getPropertiesLocation(String bundleLocation, String propertiesLocation) {
		return PropertiesLocationUtil.createPropertiesLocationFor(bundleLocation, propertiesLocation);
	}
	
	/**
	 * 是否是fragment类型的bundle
	 * 
	 * @param bundle
	 * @return
	 */
	private boolean isFragment(Bundle bundle) {		
		return this.packageAdmin.getBundleType(bundle) == PackageAdmin.BUNDLE_TYPE_FRAGMENT;			
	}
	
	/**
	 * 监控配置文件修改, 如果发生改变，则重启该接口
	 * 
	 * @param propertiesConfigs
	 * @throws Exception
	 */	
	private void monitorChgPropertiesConfigs(Map<Bundle, List<PropsConfigLocationEntry>> propertiesConfigs) throws Exception {
		for (Iterator<Map.Entry<Bundle, List<PropsConfigLocationEntry>>> it = propertiesConfigs.entrySet().iterator(); it.hasNext();) {
			Map.Entry<Bundle, List<PropsConfigLocationEntry>> entry = it.next();			
			Bundle bundle = entry.getKey();
			List<PropsConfigLocationEntry> propsConfigLocations = entry.getValue();
			
			for (PropsConfigLocationEntry propsConfigLocation : propsConfigLocations) {
				PropertiesConfig propsConfig = propsConfigLocation.getPropsConfig();
				File propsFile = propsConfigLocation.getPropsFile();
				
				// 配置文件已经被修改, 且配置文件的内容也已经被修改; 则重启Bundle
				// 文件被修改
				if (!propsFile.exists()) {
					try {
						if (!doRestartBundle(bundle)) {// restart
							it.remove(); // remove
						}
					} catch (InterruptedException e) {
						return;
					}
					continue;
				}

				if (!propsConfig.equalsWith(propsFile)) {
					PropertiesConfig npc = PropertiesConfig.getPropertiesConfig(propsFile);
					if (!propsConfig.equals(npc)) {
						log.info("Properties Changed, must restart the bundle: "
								+ bundle.getLocation());
						try {
						    doRestartBundle(bundle);// restart
						} catch (InterruptedException e) {
							return;
						}
						propsConfigLocation.setPropsConfig(npc);					
					}
				}			
			}
		}	
	}
	
	class PropsConfigLocationEntry {
		private File propsFile;
		private PropertiesConfig propsConfig;
		
		public PropsConfigLocationEntry(File propsFile, PropertiesConfig propsConfig) {
			this.propsFile = propsFile;
			this.propsConfig = propsConfig;
		}
		
		public PropertiesConfig getPropsConfig() {
			return propsConfig;
		}
		public void setPropsConfig(PropertiesConfig propsConfig) {
			this.propsConfig = propsConfig;
		}
		public File getPropsFile() {
			return propsFile;
		}
		public void setPropsFile(File propsFile) {
			this.propsFile = propsFile;
		}
	}
	
	/**
	 * 重启
	 * 
	 * @param propertiesLocation
	 * @return
	 * @throws InterruptedException 
	 */
	private boolean doRestartBundle(final Bundle bundle) throws InterruptedException {
		if (bundle.getBundleId() == this.context.getBundle().getBundleId()) {// 当前bundle
			// 本身bundle重启			
			Thread t = new Thread("Restart Framework Bundle Thread") { // 新建一个线程，避免当前线程被终止
				@Override
				public void run() {
					try {
					    doRestartBundleActionFor(bundle);	
					} catch (Throwable t) {
						log.error("Catch Exception in doRestartBundle.", t);
					}
				}
			};
			
			t.start();
			
			Object lock = new Object();			
			synchronized (lock) {				
			    lock.wait();				
			}
			
			return true;
		}
		
		return doRestartBundleActionFor(bundle);
	}
	
	/**
	 * 重启bundle
	 * 
	 * @param bundle
	 * @return
	 */
	private boolean doRestartBundleActionFor(Bundle bundle) {
		// 重启bundle
		InputStream in = null;

		try {
			in = new FileInputStream(bundle.getLocation());
			bundle.update(in);
		} catch (Exception e) {
			return false;
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (IOException e) {
				}
			}
		}
		
		if (!isFragment(bundle)) {
			try {
				bundle.start();
				refresh();
			} catch (BundleException e) {
				throw new RuntimeException(e);
			}
			
		}

		return true;
	}
	
	/**
	 * Refresh
	 */
	private void refresh() {		
		this.packageAdmin.refreshPackages(null);
	}
	
	/**
	 * 输出虚拟机中的所有线程
	 * 
	 */
	protected void printVMAllThreads() {
		Thread[] threads = new Thread[Thread.activeCount() * 2];
		Thread.enumerate(threads);
		if (threads != null) {
			for (int i = 0; i < threads.length; i ++) {
				if (threads[i] != null) {
					Thread thread = threads[i];
					log.debug("VM thread: ID=" + thread.getId()
							+ ", NAME=" + thread.getName() 
							+ ", STATE=" + thread.getState());
				}
			}
		}
	}
}
